package drds.common.db.xml_mapping.builder.xml;

import drds.common.db.xml_mapping.builder.BaseBuilder;
import drds.common.db.xml_mapping.builder.BuilderException;
import drds.common.db.xml_mapping.datasource.DataSourceFactory;
import drds.common.db.xml_mapping.io.Resources;
import drds.common.db.xml_mapping.mapping.Environment;
import drds.common.db.xml_mapping.parsing.XmlNode;
import drds.common.db.xml_mapping.parsing.XmlNodeParser;
import drds.common.db.xml_mapping.session.Configuration;
import drds.common.db.xml_mapping.transaction.JdbcTransactionFactory;
import drds.common.db.xml_mapping.transaction.TransactionFactory;
import lombok.extern.slf4j.Slf4j;

import javax.sql.DataSource;
import java.io.InputStream;
import java.io.Reader;
import java.util.Properties;

@Slf4j
public class XmlConfigurationBuilder extends BaseBuilder {

  private final XmlNodeParser xmlNodeParser;
  private boolean parsed;
  private String id;


  public XmlConfigurationBuilder(Reader reader, String id, Properties props) {
    this(new XmlNodeParser(reader, true, props, new XmlMapperEntityResolver()), id, props);
  }


  public XmlConfigurationBuilder(InputStream inputStream, String id, Properties props) {
    this(new XmlNodeParser(inputStream, true, props, new XmlMapperEntityResolver()), id, props);
  }

  private XmlConfigurationBuilder(XmlNodeParser xmlNodeParser, String id, Properties variables) {
    super(new Configuration());
    this.configuration.setVariables(variables);
    this.parsed = false;
    this.id = id;
    this.xmlNodeParser = xmlNodeParser;
  }

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XmlConfigurationBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(xmlNodeParser.evaluateNode("/configuration"));
    return configuration;
  }

  private void parseConfiguration(XmlNode root) {
    try {
      environmentsElement(root.evaluateNode("environments"));
      typeAliasesElement(root.evaluateNode("typeAliases"));
      mapperElement(root.evaluateNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }


  private void environmentsElement(XmlNode xmlNodeList) throws Exception {
    if (xmlNodeList != null) {
      for (XmlNode xmlNode : xmlNodeList.getChildrenXmlNodeList()) {
        String id = xmlNode.getStringAttribute("id");
        TransactionFactory transactionFactory = new JdbcTransactionFactory();
        DataSourceFactory dataSourceFactory = dataSourceElement(xmlNode.evaluateNode("dataSource"));
        DataSource dataSource = dataSourceFactory.getDataSource();
        Environment environment = new Environment(id, transactionFactory, dataSource);
        configuration.setEnvironment(environment);
      }
    }
  }


  private DataSourceFactory dataSourceElement(XmlNode xmlNode) throws Exception {
    DataSourceFactory dataSourceFactory = new DataSourceFactory() {
      @Override
      public DataSource getDataSource() {
        DataSource dataSource = null;
        return dataSource;
      }
    };
    return dataSourceFactory;
  }

  private void typeAliasesElement(XmlNode parent) {
    if (parent != null) {
      for (XmlNode packageXmlNode : parent.getChildrenXmlNodeList()) {
        if ("package".equals(packageXmlNode.getName())) {
          @SuppressWarnings("")
          String packageName = packageXmlNode.getStringAttribute("packageName");
          String packageAlias = packageXmlNode.getStringAttribute("alias");
          //
          for (XmlNode classXmlNode : parent.getChildrenXmlNodeList()) {
            String classType = classXmlNode.getStringAttribute("parameter_and_result_value_type");
            String classAlias = classXmlNode.getStringAttribute("alias");
            try {
              Class<?> clazz = Resources.classForName(classType);
              String packageAliasAndClassAlias = packageAlias + "." + classAlias;
              typeAliasRegistry.registerAlias(packageAliasAndClassAlias, clazz);
            } catch (ClassNotFoundException e) {
              throw new BuilderException("Error registering typeAlias for '" + classAlias + "'. Cause: " + e, e);
            }
          }

        }
      }
    }
  }


  private void mapperElement(XmlNode xmlNode) throws Exception {
    if (xmlNode != null) {
      for (XmlNode child : xmlNode.getChildrenXmlNodeList()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String mapperClass = child.getStringAttribute("class");
          //resource
          if (resource != null && mapperClass == null) {
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XmlMapperBuilder xmlMapperBuilder = new XmlMapperBuilder(inputStream, configuration, resource, configuration.getSqlRefIdToXmlNodeMap());
            xmlMapperBuilder.parse();
          } else if (resource == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }


}
